package com.gitee.dbswitch.admin.service;

import com.gitee.dbswitch.admin.common.exception.DbswitchException;
import com.gitee.dbswitch.admin.common.response.PageResult;
import com.gitee.dbswitch.admin.common.response.Result;
import com.gitee.dbswitch.admin.common.response.ResultCode;
import com.gitee.dbswitch.admin.controller.converter.APIAssignmentDetailConverter;
import com.gitee.dbswitch.admin.controller.converter.APIAssignmentInfoConverter;
import com.gitee.dbswitch.admin.controller.converter.AssignmentInfoConverter;
import com.gitee.dbswitch.admin.dao.APIAssignmentConfigDAO;
import com.gitee.dbswitch.admin.dao.AssignmentTaskDAO;
import com.gitee.dbswitch.admin.dao.DatabaseConnectionDAO;
import com.gitee.dbswitch.admin.entity.APIAssignmentConfigEntity;
import com.gitee.dbswitch.admin.entity.AssignmentTaskEntity;
import com.gitee.dbswitch.admin.entity.DatabaseConnectionEntity;
import com.gitee.dbswitch.admin.model.request.*;
import com.gitee.dbswitch.admin.model.response.APIAssignmentDetailResponse;
import com.gitee.dbswitch.admin.model.response.APIAssignmentInfoResponse;
import com.gitee.dbswitch.admin.type.ConfigTypeEnum;
import com.gitee.dbswitch.admin.type.ScheduleModeEnum;
import com.gitee.dbswitch.admin.type.TaskTypeEnum;
import com.gitee.dbswitch.admin.util.PageUtils;
import com.gitee.dbswitch.common.converter.ConverterFactory;
import com.gitee.dbswitch.common.type.ProductTypeEnum;
import com.gitee.dbswitch.data.config.DbswtichPropertiesConfiguration;
import com.gitee.dbswitch.data.entity.APIDataSourceProperties;
import com.gitee.dbswitch.data.entity.GlobalParamConfigProperties;
import com.gitee.dbswitch.data.entity.TargetDataSourceProperties;
import com.gitee.dbswitch.data.util.JsonUtils;
import com.gitee.dbswitch.data.util.OKHttpUtils;
import com.google.common.collect.Lists;
import com.google.gson.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.io.File;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;


/**
 * api task service
 *
 * @author natural
 */
@Service
public class APITaskService {

    @Resource
    private AssignmentTaskDAO assignmentTaskDAO;

    @Resource
    private APIAssignmentConfigDAO apiAssignmentConfigDAO;

    @Resource
    private DatabaseConnectionDAO databaseConnectionDAO;

    @Resource
    private DriverLoadService driverLoadService;

    @Resource
    private ScheduleService scheduleService;

    @Transactional(rollbackFor = Exception.class)
    public APIAssignmentInfoResponse createAPI(APITaskCreateRequest request) {
        AssignmentTaskEntity assignment = request.toAPITask();
        assignment.setTaskType(TaskTypeEnum.APIASSIGNMENT.getValue());
        assignmentTaskDAO.insert(assignment);
        APIAssignmentConfigEntity assignmentConfigEntity = request.toAPIConfig(assignment.getId());
        apiAssignmentConfigDAO.insert(assignmentConfigEntity);
        return ConverterFactory.getConverter(APIAssignmentInfoConverter.class)
                .convert(assignmentTaskDAO.getById(assignment.getId()));
    }

    public PageResult<APIAssignmentInfoResponse> listAll(AssignmentSearchRequest request) {
        Supplier<List<APIAssignmentInfoResponse>> method = () ->
                ConverterFactory.getConverter(APIAssignmentInfoConverter.class)
                        .convert(assignmentTaskDAO.listAll(request.getSearchText(),TaskTypeEnum.APIASSIGNMENT.getValue()));
        return PageUtils.getPage(method, request.getPage(), request.getSize());
    }

    @Transactional(rollbackFor = Exception.class)
    public void deployAssignments(List<Long> ids) {
        checkAssignmentAllExist(ids);
        ids.forEach(id -> {
            AssignmentTaskEntity assignmentTaskEntity = assignmentTaskDAO.getById(id);
            if (assignmentTaskEntity.getPublished()) {
                throw new DbswitchException(ResultCode.ERROR_RESOURCE_HAS_DEPLOY, "ID=" + id);
            }
        });

        for (Long id : ids) {
            AssignmentTaskEntity assignmentTaskEntity = assignmentTaskDAO.getById(id);
            APIAssignmentConfigEntity assignmentConfigEntity = apiAssignmentConfigDAO.getByAssignmentTaskId(id);

            DbswtichPropertiesConfiguration properties = new DbswtichPropertiesConfiguration();
            properties.setTarget(this.getTargetDataSourceProperties(assignmentConfigEntity));
            properties.setConfig(this.getGlobalParamConfigProperties(assignmentConfigEntity));
            properties.setApi(this.getGlobalParamAPIProperties(assignmentConfigEntity));
            assignmentTaskEntity.setPublished(Boolean.TRUE);
            assignmentTaskEntity.setContent(JsonUtils.toJsonString(properties));
            assignmentTaskDAO.updateById(assignmentTaskEntity);

            ScheduleModeEnum systemScheduled = ScheduleModeEnum.SYSTEM_SCHEDULED;
            if (assignmentTaskEntity.getScheduleMode() == systemScheduled) {
                scheduleService.scheduleTask(assignmentTaskEntity.getId(), systemScheduled, ConfigTypeEnum.ASSIGNMENT_CONFIG.getValue());
            }
        }

    }

    @Transactional(rollbackFor = Exception.class)
    public void runAPI(List<Long> ids) {
        checkAPIAllExist(ids);
        List<AssignmentTaskEntity> tasks = new ArrayList<>();
        for (Long id : ids) {
            AssignmentTaskEntity assignmentTaskEntity = assignmentTaskDAO.getById(id);
            if (assignmentTaskEntity.getPublished()) {
                tasks.add(assignmentTaskEntity);
            } else {
                throw new DbswitchException(ResultCode.ERROR_RESOURCE_NOT_DEPLOY, assignmentTaskEntity.getName());
            }
        }

        tasks.forEach(assignmentTask -> {
            scheduleService.scheduleTask(assignmentTask.getId(), ScheduleModeEnum.MANUAL, ConfigTypeEnum.API_ASSIGNMENT_CONFIG.getValue());
        });

    }

    private void checkAPIAllExist(List<Long> ids) {
        for (Long id : ids) {
            AssignmentTaskEntity assignmentTaskEntity = assignmentTaskDAO.getById(id);
            if (Objects.isNull(assignmentTaskEntity)) {
                throw new DbswitchException(ResultCode.ERROR_RESOURCE_NOT_EXISTS, "ID=" + id);
            }
            APIAssignmentConfigEntity apiAssignmentConfigEntity = apiAssignmentConfigDAO.getByAssignmentTaskId(id);
            Long targetConnectionId = apiAssignmentConfigEntity.getTargetConnectionId();
            // 检查任务对应的源端和目标端连接是否还存在
            List<Long> connectionIds = Lists.newArrayList(targetConnectionId);
            if (databaseConnectionDAO.getByIds(connectionIds).size() != connectionIds.size()) {
                throw new DbswitchException(ResultCode.ERROR_RESOURCE_NOT_EXISTS,
                        "ConnectionID=" + connectionIds);
            }
        }
    }

    private TargetDataSourceProperties getTargetDataSourceProperties(
            APIAssignmentConfigEntity apiAssignmentConfig) {
        TargetDataSourceProperties targetDataSourceProperties = new TargetDataSourceProperties();
        DatabaseConnectionEntity targetDatabaseConnectionEntity = databaseConnectionDAO
                .getById(apiAssignmentConfig.getTargetConnectionId());
        File driverVersionFile = driverLoadService.getVersionDriverFile(
                targetDatabaseConnectionEntity.getType(),
                targetDatabaseConnectionEntity.getVersion());
        targetDataSourceProperties.setUrl(targetDatabaseConnectionEntity.getUrl());
        targetDataSourceProperties.setDriverClassName(targetDatabaseConnectionEntity.getDriver());
        targetDataSourceProperties.setDriverPath(driverVersionFile.getAbsolutePath());
        targetDataSourceProperties.setUsername(targetDatabaseConnectionEntity.getUsername());
        targetDataSourceProperties.setPassword(targetDatabaseConnectionEntity.getPassword());
        targetDataSourceProperties.setTargetSchema(apiAssignmentConfig.getTargetSchema());
        if (apiAssignmentConfig.getTargetDropTable()) {
            targetDataSourceProperties.setTargetDrop(Boolean.TRUE);
            targetDataSourceProperties.setChangeDataSync(Boolean.FALSE);
        } else {
            targetDataSourceProperties.setTargetDrop(Boolean.FALSE);
            targetDataSourceProperties.setChangeDataSync(Boolean.TRUE);
        }
        if (apiAssignmentConfig.getTargetOnlyCreate()) {
            targetDataSourceProperties.setOnlyCreate(Boolean.TRUE);
        }
        if (apiAssignmentConfig.getTargetAutoIncrement()) {
            targetDataSourceProperties.setCreateTableAutoIncrement(Boolean.TRUE);
        }
        targetDataSourceProperties.setTableNameCase(apiAssignmentConfig.getTableNameCase());
        targetDataSourceProperties.setColumnNameCase(apiAssignmentConfig.getColumnNameCase());
        targetDataSourceProperties.setTargetSyncOption(apiAssignmentConfig.getTargetSyncOption());
        targetDataSourceProperties.setBeforeSqlScripts(apiAssignmentConfig.getBeforeSqlScripts());
        targetDataSourceProperties.setAfterSqlScripts(apiAssignmentConfig.getAfterSqlScripts());

        return targetDataSourceProperties;
    }

    private APIDataSourceProperties getGlobalParamAPIProperties(
            APIAssignmentConfigEntity apiAssignmentConfig) {
        APIDataSourceProperties configProperties = new APIDataSourceProperties();
        configProperties.setApiUrl(apiAssignmentConfig.getApiURL());
        configProperties.setRequestMethod(apiAssignmentConfig.getRequestMethod());
        configProperties.setTargetTable(apiAssignmentConfig.getTargetTable());
        configProperties.setSynchronizeType(apiAssignmentConfig.getSynchronizeType());
        configProperties.setRegexTableMapper(apiAssignmentConfig.getTableNameMap());
        configProperties.setRegexColumnMapper(apiAssignmentConfig.getColumnNameMap());
        return configProperties;
    }

    private GlobalParamConfigProperties getGlobalParamConfigProperties(
            APIAssignmentConfigEntity apiAssignmentConfig) {
        GlobalParamConfigProperties configProperties = new GlobalParamConfigProperties();
        configProperties.setChannelQueueSize(apiAssignmentConfig.getChannelSize());
        return configProperties;
    }

    private void checkAssignmentAllExist(List<Long> ids) {
        for (Long id : ids) {
            AssignmentTaskEntity assignmentTaskEntity = assignmentTaskDAO.getById(id);
            if (Objects.isNull(assignmentTaskEntity)) {
                throw new DbswitchException(ResultCode.ERROR_RESOURCE_NOT_EXISTS, "ID=" + id);
            }
            APIAssignmentConfigEntity apiAssignmentConfig = apiAssignmentConfigDAO.getByAssignmentTaskId(id);
            Long targetConnectionId = apiAssignmentConfig.getTargetConnectionId();
            // 检查任务对应的源端和目标端连接是否还存在
            List<Long> connectionIds = Lists.newArrayList(targetConnectionId);
            if (databaseConnectionDAO.getByIds(connectionIds).size() != connectionIds.size()) {
                throw new DbswitchException(ResultCode.ERROR_RESOURCE_NOT_EXISTS,
                        "ConnectionID=" + connectionIds);
            }
        }
    }

    public Result<APIAssignmentDetailResponse> detailAssignment(Long id) {
        AssignmentTaskEntity assignmentTaskEntity = assignmentTaskDAO.getById(id);
        if (Objects.isNull(assignmentTaskEntity)) {
            return Result.failed(ResultCode.ERROR_RESOURCE_NOT_EXISTS, "ID=" + id);
        }

        APIAssignmentDetailResponse detailResponse = ConverterFactory
                .getConverter(APIAssignmentDetailConverter.class).convert(assignmentTaskEntity);
        return Result.success(detailResponse);
    }

    public Result<APIAssignmentInfoResponse> infoAssignment(Long id) {
        AssignmentTaskEntity assignmentTaskEntity = assignmentTaskDAO.getById(id);
        if (Objects.isNull(assignmentTaskEntity)) {
            return Result.failed(ResultCode.ERROR_RESOURCE_NOT_EXISTS, "ID=" + id);
        }

        APIAssignmentInfoResponse infoResponse = ConverterFactory
                .getConverter(APIAssignmentInfoConverter.class).convert(assignmentTaskEntity);
        return Result.success(infoResponse);
    }

    @Transactional(rollbackFor = Exception.class)
    public void retireAssignments(List<Long> ids) {
        checkAssignmentAllExist(ids);
        for (Long id : ids) {
            AssignmentTaskEntity assignmentTaskEntity = assignmentTaskDAO.getById(id);
            if (Objects.nonNull(assignmentTaskEntity.getPublished())
                    && assignmentTaskEntity.getPublished()) {
                String jobKey = assignmentTaskEntity.getJobKey();
                scheduleService.cancelByJobKey(jobKey);
                scheduleService.cancelManualJob(id);
                assignmentTaskEntity.setPublished(Boolean.FALSE);
                assignmentTaskEntity.setContent("{}");
                assignmentTaskEntity.setJobKey("");
                assignmentTaskDAO.updateById(assignmentTaskEntity);
            }
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void updateAssignment(APIAssignmentUpdateRequest request) {
        AssignmentTaskEntity assignmentTaskEntity = assignmentTaskDAO.getById(request.getId());
        if (Objects.isNull(assignmentTaskEntity)) {
            throw new DbswitchException(ResultCode.ERROR_RESOURCE_NOT_EXISTS, "ID=" + request.getId());
        } else if (assignmentTaskEntity.getPublished()) {
            throw new DbswitchException(ResultCode.ERROR_RESOURCE_HAS_DEPLOY, "ID=" + request.getId());
        }

        AssignmentTaskEntity newAssignmentTaskEntity = request.toAPIAssignmentTask();
        assignmentTaskDAO.updateById(newAssignmentTaskEntity);

        APIAssignmentConfigEntity apiAssignmentConfigEntity = request
                .toAPIAssignmentConfig(assignmentTaskEntity.getId());
        apiAssignmentConfigDAO.deleteByAssignmentTaskId(assignmentTaskEntity.getId());
        apiAssignmentConfigDAO.insert(apiAssignmentConfigEntity);

        Long targetConnectionId = apiAssignmentConfigEntity.getTargetConnectionId();
        DatabaseConnectionEntity entity = databaseConnectionDAO.getById(targetConnectionId);
        if (ProductTypeEnum.SQLITE3 == entity.getType()) {
            if (ProductTypeEnum.isUnsupportedTargetSqlite(entity.getUrl())) {
                throw new DbswitchException(ResultCode.ERROR_INVALID_ASSIGNMENT_CONFIG,
                        "不支持目的端数据源为远程服务器上的SQLite或内存方式下的SQLite");
            }
        }
    }

    public String testHttpRequest(TestHttpRequest request) {
        Map<String,String> paramsTemp= convertListToMap(request.getApiParams());
        Map<String,String> headersTemp= convertListToMap(request.getApiHeaders());
        String s = OKHttpUtils.doRequest(request.getApiURL(), paramsTemp, headersTemp, request.getApiBody(), request.getRequestMethod()).toString();
        JsonElement jsonElement = JsonParser.parseString(s);
       if(request.getApiJsonPosition()!=null&&request.getApiJsonRoot()!=null){
           jsonElement = extractJsonObject(jsonElement,request.getApiJsonRoot(),Integer.parseInt(request.getApiJsonPosition()));
        }else {
           jsonElement = processJson(jsonElement);
       }
        return jsonElement.toString();
    }

    public static Map<String, String> convertListToMap(List<Map<String, String>> list) {
        return list.stream()
                .collect(Collectors.toMap(
                        entry -> entry.get("keyName"),
                        entry -> entry.get("valueName")
                ));
    }

    private static JsonObject extractJsonObject(JsonElement jsonElement, String apiJsonKey, int apiKeyPosition) {
        // 确保传入的 JsonElement 是一个 JsonObject
        if (jsonElement.isJsonObject()) {
            JsonObject jsonObject = jsonElement.getAsJsonObject();
            // 检查是否包含指定的 apiJsonKey
            if (jsonObject.has(apiJsonKey)) {
                JsonArray jsonArray = jsonObject.getAsJsonArray(apiJsonKey);
                // 检查位置是否在数组范围内
                if (apiKeyPosition > 0 && apiKeyPosition <= jsonArray.size()) {
                    JsonElement targetElement = jsonArray.get(apiKeyPosition - 1);
                    // 检查目标元素是否为对象
                    if (targetElement.isJsonObject()) {
                        JsonObject targetObject = targetElement.getAsJsonObject();
                        // 如果目标对象中包含嵌套的对象，递归查找
                        if (targetObject.has("data")) {
                            JsonArray nestedArray = targetObject.getAsJsonArray("data");
                            if (!nestedArray.isEmpty()) {
                                JsonElement nestedElement = nestedArray.get(0);
                                if (nestedElement.isJsonObject()) {
                                    return nestedElement.getAsJsonObject(); // 返回第一个嵌套对象
                                }
                            }
                        }

                        return targetObject; // 返回找到的目标对象
                    }
                }
            } else {
                throw new DbswitchException(ResultCode.ERROR_INVALID_ARGUMENT,
                        "Key not found: " + apiJsonKey);
            }
        } else {
            throw new DbswitchException(ResultCode.ERROR_INVALID_ARGUMENT,
                    "Input JsonElement is not a JsonObject");
        }

        return new JsonObject(); // 返回空的 JSON 对象
    }
    private static JsonObject processJson(JsonElement jsonElement) {
        if (jsonElement.isJsonArray()) {
            JsonArray jsonArray = jsonElement.getAsJsonArray();
            if (!jsonArray.isEmpty()) {
                JsonElement firstElement = jsonArray.get(0);
                // 仅处理数组的第一个元素，不进行递归
                if (firstElement.isJsonObject()) {
                        return firstElement.getAsJsonObject(); // 返回当前对象
                } else {
                    return new JsonObject(); // 返回一个空的 JSON 对象
                }
            } else {
                return new JsonObject(); // 返回一个空的 JSON 对象
            }
        } else if (jsonElement.isJsonObject()) {
            // 可以在这里处理具体的 JSON 对象
            return jsonElement.getAsJsonObject(); // 返回当前对象
        } else {
            return new JsonObject(); // 返回一个空的 JSON 对象
        }
    }
}
